package net.heavencraft.bejeweled;

import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

import org.javatuples.Pair;

import net.heavencraft.bejeweled.jewel.CreeperJewel;
import net.heavencraft.bejeweled.jewel.Jewel;
import net.heavencraft.bejeweled.jewel.StreakMultiplier;
import net.heavencraft.bejeweled.util.ImageProvider;
import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PImage;

/**
 * Represents The game Bejeweled. This is the gamebase and has core
 * 'engine'-like functionality
 * 
 * @author Sven
 * @author Wesley
 * 
 */
public class Bejeweled extends PApplet {

	/**
	 * Serial version for Processing Applet
	 */
	private static final long serialVersionUID = 6434955317740096038L;

	/**
	 * Score of the game
	 */
	public int points;

	/**
	 * The dimensions of the screen
	 */
	public static final Rectangle screenDimsensions = new Rectangle(800, 600);

	/**
	 * The size of the jewels in pixels. This is also the size of the images
	 * that the form the textures.
	 */
	public static final int jewelSize = 50;

	/**
	 * The size of the gameboard
	 */
	public static final int jewels = 8;

	/**
	 * The background color
	 */
	public static final int background = Color.WHITE.getRGB();

	/**
	 * The background image;
	 */
	public final PImage backgroundImage = this.loadImage("stone.png");

	/**
	 * Crosshair of selected Jewel
	 */
	public final PImage selectedCrosshair = this
			.loadImage("selectedCrosshair.png");

	/**
	 * The header of on the top of the screen
	 */
	public final PImage ui_topBar = this.loadImage("uiTop.png");

	/**
	 * The font of the game
	 */
	public final PFont minecraftFont = this.loadFont("Minecraft.vlw");

	/**
	 * Provides images for the Jewel textures. This is in the main class because
	 * Processing needs the PApplet to load an image
	 */
	public ImageProvider images = new ImageProvider(this);
	/**
	 * A list of objects that will be drawn to the screen. Any objects that must
	 * be drawn should be in this list.
	 */
	private ArrayList<Drawable> gameobjects = new ArrayList<Drawable>();
	/**
	 * Represents the grid for the jewels. A reference is also added the
	 * {@link #gameobjects} list so it will be drawn
	 */
	public GameGrid grid;

	/**
	 * Main class method
	 * 
	 * @param args
	 *            Passed commandline arguments
	 */
	public static void main(String args[]) {
		PApplet.main("net.heavencraft.bejeweled.Bejeweled");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see processing.core.PApplet#setup()
	 */
	@Override
	public void setup() {
		this.size(screenDimsensions.width, screenDimsensions.height);

		this.grid = new GameGrid(this, jewels, jewels, new Point(200, 175));
		this.gameobjects.add(this.grid);

		grid.generateGrid();
		mapChanged();
		this.textAlign(LEFT, TOP);
		this.textSize(20F);
		this.textFont(minecraftFont);
		points = 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see processing.core.PApplet#draw()
	 */
	@Override
	public void draw() {
		this.tick();

		this.clear();
		this.background(background);

		/* Background */
		for (int i = 0; i < 25; i++) {
			for (int j = 0; j < 19; j++) {
				image(backgroundImage, i * 32, j * 32);
			}
		}

		/* Topbar */
		for (int i = 0; i < 17; i++) {
			image(ui_topBar, i * 48, 0);
		}

		for (Drawable obj : gameobjects) {
			obj.draw();
		}

		/* Score */
		this.fill(Color.YELLOW.getRGB());
		this.text("Points: " + points, 10, 10);
		this.fill(Color.WHITE.getRGB());
	}

	/**
	 * Performs Bejeweled Gametick
	 */
	public void tick() {
		if (validateSelected()) {
			switchSelected();
		}
	}

	/**
	 * Validaties if the selected jewels are neighbours
	 * 
	 * @return True if selected jewels are neighbours
	 */
	private boolean validateSelected() {
		Jewel[] selectedJewels = grid.getSelectedObjects()
				.toArray(new Jewel[0]);
		if (selectedJewels.length > 2) {
			grid.unselectAllObjects();
			return false;
		} else if (selectedJewels.length == 2) {
			if (selectedJewels[0].isNeighbourWith(selectedJewels[1])) {
				System.out.println("yup");
				return true;
			} else {
				System.out.println("nope");
				selectedJewels[0].toggleSelected();
				selectedJewels[1].toggleSelected();
			}
		}
		return false;
	}

	/**
	 * Called when map is changed and needs cascading and rules to be applied.
	 */
	public void mapChanged() {
		List<Pair<Jewel, Point>> specialty = new ArrayList<>();

		System.out.println("Paths X");
		List<List<Point>> stacksX = grid.findStacksX();
		for (List<Point> list : stacksX) {

			System.out.print("[");
			for (Point pair : list) {
				System.out.print(pair);
			}
			System.out.println("]");
		}
		System.out.println("Paths Y");
		List<List<Point>> stacksY = grid.findStacksY();
		for (List<Point> list : stacksY) {
			System.out.print("[");
			for (Point pair : list) {
				System.out.print(pair);
			}
			System.out.println("]");
		}

		int pointsToAdd = 0;

		for (List<Point> list : stacksX) {
			if (list.size() == 4) {
				specialty.add(new Pair<>((Jewel)new CreeperJewel(this, grid
						.getObjectAt(list.get(0).x, list.get(0).y).getColor()),
						list.get(0)));
			} else if (list.size() == 5) {
				// TODO: Add ender Jewel
			}
			int pointsForRow = 0;
			for (Point pair : list) {
				pointsForRow += grid.getObjectAt((int) pair.getX(),
						(int) pair.getY()).remove(null);
			}
			pointsToAdd += pointsForRow
					* StreakMultiplier.getMultiplier(list.size()).multiplier;
		}

		for (List<Point> list : stacksY) {
			if (list.size() == 4) {
				specialty.add(new Pair<>((Jewel)new CreeperJewel(this, grid
						.getObjectAt(list.get(0).x, list.get(0).y).getColor()),
						list.get(0)));
			} else if (list.size() == 5) {
				// TODO: Add ender Jewel
			}
			int pointsForRow = 0;
			for (Point pair : list) {
				if (grid.getObjectAt((int) pair.getX(), (int) pair.getY()) == null)
					continue;
				pointsForRow += grid.getObjectAt((int) pair.getX(),
						(int) pair.getY()).remove(null);
			}
			pointsToAdd += pointsForRow
					* StreakMultiplier.getMultiplier(list.size()).multiplier;
		}

		points += pointsToAdd;

		for (Pair<Jewel, Point> j : specialty) {
			grid.setJewelAt(j.getValue0(), j.getValue1().x, j.getValue1().y);
		}

		grid.cascade();
	}

	/**
	 * Exchanges the two selected jewels.
	 */
	public void switchSelected() {
		Jewel[] selected = grid.getSelectedObjects().toArray(new Jewel[0]);
		grid.switchObjects(selected[0], selected[1]);
		for (Jewel j : selected)
			j.toggleSelected();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see processing.core.PApplet#mouseClicked()
	 */
	@Override
	public void mouseReleased() {
		Jewel o = grid.getObjectAtLocation(this.mouseX, this.mouseY);
		if (o == null)
			return;
		o.toggleSelected();
	}
}